#include <p18cxxx.h>
#include <portb.h>
#include <timers.h>
#include <usart.h>
#include "main.h"

#pragma config OSC = HS, IESO = OFF, DEBUG = OFF, PWRT = OFF, BOREN = OFF, WDT = OFF, MCLRE = ON, PBADEN = OFF

unsigned char   sigData[MAX_BYTE];
unsigned char*  sigBytePtr;
unsigned char   sigBitPtr;

void main(void) {
    unsigned char byte;
    
    init();
    
    while (1) {
        if (DataRdyUSART()) {
            byte = getcUSART();
            params.byte = byte;
        }
    }
}

void init(void) {
    // disable global interrupts
    INTCONbits.GIEH = 0;
    
    // init parameters
    params.writeLen = 1;
    
    // init state machine
    state = STATE_WAIT_SIG;
    
    // init IR Input attached at RB1 pin
    OpenRB1INT(PORTB_PULLUPS_ON | FALLING_EDGE_INT);
    
    // init serial port
    baudUSART(
            BAUD_IDLE_TX_PIN_STATE_HIGH
            & BAUD_IDLE_RX_PIN_STATE_HIGH
            & BAUD_16_BIT_RATE
            & BAUD_WAKEUP_OFF
            & BAUD_AUTO_OFF);
    OpenUSART(
            USART_ASYNCH_MODE
            & USART_EIGHT_BIT
            & USART_CONT_RX
            & USART_ADDEN_OFF
            & USART_BRGH_HIGH
            & USART_TX_INT_OFF
            & USART_RX_INT_OFF, USART_BRG);
    
    // enable global interrupts
    INTCONbits.GIEH = 1;
}

void onSigHead(void) {
    // open timer0
    OpenTimer0(TIMER_INT_ON & T0_SOURCE_INT & T0_16BIT);
    WriteTimer0(TIMER_INIT_VAL);
}

void onSigStart(void) {
    unsigned char i;
    
    // reset signal data
    for (i = 0; i < sizeof(sigData); i++) {
        sigData[i] = 0;
    }
    sigBytePtr = sigData;
    sigBitPtr = 0;
}

void onSigBitRecv(unsigned char b) {
    if ((sigBytePtr - sigData) >= MAX_BYTE) {
        return;
    }
    
    if (b) {
        (*sigBytePtr) |= 1 << sigBitPtr;
    }
    
    sigBitPtr++;
    if (sigBitPtr >= 8) {
        sigBitPtr = 0;
        sigBytePtr++;
    }
}

void onSigEndSuccess(void) {
    unsigned char* sigPtr = sigData;
    
    // close timer0
    CloseTimer0();
    
    // write signal length if configured
    if (params.writeLen) {
        putByteUSART((unsigned char) (sigBytePtr - sigData));
    }
    
    // write signal through serial port
    while (sigPtr < sigBytePtr) {
        putByteUSART(*(sigPtr++));
    }
}

void onSigEndError(void) {
    // close timer0
    CloseTimer0();
}

void putByteUSART(unsigned char byte) {
    while (BusyUSART());
    putcUSART(byte);
}

#pragma code InterruptVectorHigh = 0x08
void InterruptVectorHigh(void) {
    _asm
        goto InterruptHandlerHigh //jump to interrupt routine
    _endasm
}

#pragma code
#pragma interrupt InterruptHandlerHigh
void InterruptHandlerHigh() {
    unsigned int sigCount = getTimer0Count();
    
    WriteTimer0(TIMER_INIT_VAL);
    
    if (isIrInInt()) {
        clrIrInInt();
        
        switch (state) {
        case STATE_WAIT_SIG:
            setIrInIntOnHigh();
            state = STATE_SIG_HEAD1;
            onSigHead();
            break;
            
        case STATE_SIG_HEAD1:
            setIrInIntOnLow();
            if (sigCount >= BEGIN_COUNT) {
                state = STATE_SIG_HEAD2;
                onSigStart();
            } else {
                state = STATE_WAIT_SIG;
                onSigEndError();
            }
            break;
            
        case STATE_SIG_HEAD2:
            setIrInIntOnHigh();
            state = STATE_IN_SIG;
            break;
            
        case STATE_IN_SIG:
            if (isIrInIntOnHigh()) {
                setIrInIntOnLow();
                if (sigCount < BIT_BEGIN_COUNT) {
                    state = STATE_WAIT_SIG;
                    onSigEndError();
                }
            } else {
                setIrInIntOnHigh();
                if (sigCount < BIT_1_COUNT) {
                    onSigBitRecv(0);
                } else {
                    onSigBitRecv(1);
                }
            }
            break;
        }
    } else if (isTimer0Int()) {
        clrTimer0Int();
        
        if ((state == STATE_IN_SIG)
                && isIrInIntOnLow()
                && (sigBytePtr > sigData)
                && (sigBitPtr == 0)) {
            onSigEndSuccess();
        } else {
            onSigEndError();
        }
        
        state = STATE_WAIT_SIG;
        setIrInIntOnLow();
    }
}